package com.google.code.virtualhockey.vhx.sds

import com.google.appengine.api.datastore.Key
import com.google.appengine.api.datastore.KeyFactory
import scala.reflect.Manifest

/**
 * Represents a type-safe key for a persistent entity.
 *
 * @param A the type of entity this key is targeted at
 */
sealed abstract class SKey[A <: STEntity[A]] protected ( protected val parent: Option[SKey[_]], protected[sds] val cls: Class[A] ) {

  type TId

  val id: TId

  private def canEqual( other: Any ) = other.isInstanceOf[SKey[_]]

  override def equals( other: Any ) = other match {
    case that: SKey[_] => ( that canEqual this ) && this.parent == that.parent && this.cls == that.cls && this.id == that.id
    case _             => false
  }

  override lazy val hashCode: Int =
    41 * ( 41 * ( 41 + parent.hashCode ) + cls.hashCode ) + id.hashCode

  override lazy val toString: String = cls.getName + "(" + id.toString + ")"

  def toGaeKey(): Key
}

/**
 * A key for objects which use Long as identifier.
 */
final class SIdKey[A <: STEntity[A]] private[sds] ( parent: Option[SKey[_]], cls: Class[A], keyId: Long ) extends SKey[A]( parent, cls ) {

  def this( parent: Option[SKey[_]], keyId: Long )( implicit manifestA: Manifest[A] ) =
    this( parent, manifestA.erasure.asInstanceOf[Class[A]], keyId )

  def this( keyId: Long )( implicit manifestA: Manifest[A] ) =
    this( None, manifestA.erasure.asInstanceOf[Class[A]], keyId )

  type TId = Long
  val id = keyId

  override lazy val toGaeKey: Key = KeyFactory.createKey( if ( parent == None ) null else parent.get.toGaeKey, Sds.schema.get.getMetaData( cls ).kind, id )
}

/**
 * A key for objects that use self-managed strings as identifier.
 */
final class SNameKey[A <: STEntity[A]] private[sds] ( parent: Option[SKey[_]], cls: Class[A], keyName: String ) extends SKey[A]( parent, cls ) {
  def this( parent: Option[SKey[_]], keyName: String )( implicit manifestA: Manifest[A] ) =
    this( parent, manifestA.erasure.asInstanceOf[Class[A]], keyName )

  def this( keyName: String )( implicit manifestA: Manifest[A] ) =
    this( None, manifestA.erasure.asInstanceOf[Class[A]], keyName )

  type TId = String
  val id = keyName

  override lazy val toGaeKey: Key = KeyFactory.createKey( if ( parent == None ) null else parent.get.toGaeKey, Sds.schema.get.getMetaData( cls ).kind, id )
}

/**
 * Companion object for SdsKey acting as key factory.
 */
object SKey {

  /**
   * Creates an SKey based on the given native Google App Engine Datastore Key.
   *
   * @param key the Key to be used as source for the SKey
   * @return either an SIdKey or an SNameKey based on the given GAE Key
   */
  def fromGaeKey[A <: STEntity[A]]( key: Key ): SKey[A] = {
    val cls = Sds.schema.get.getMetaData( key.getKind ).asInstanceOf[Class[A]]
    fromGaeKey( key, cls )
  }

  /**
   * Creates an SKey based on the given native Google App Engine Datastore Key.
   *
   * @param key the Key to be used as source for the SKey
   * @param cls the Class of the entity for which to create an SKey
   * @return either an SIdKey or an SNameKey based on the given GAE Key
   */
  def fromGaeKey[A <: STEntity[A]]( key: Key, cls: Class[A] ): SKey[A] = {

    val keyParent: Option[SKey[_]] =
      if ( key.getParent == null )
        None
      else
        Some( fromGaeKey( key.getParent, Sds.schema.get.getMetaData( key.getKind ).cls ) )

    if ( key.getName == null )
      new SIdKey[A]( keyParent, cls, key.getId )
    else
      new SNameKey[A]( keyParent, cls, key.getName )
  }
}